package com.osfw.framework.web.service;

import cn.dev33.satoken.session.SaSession;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.temp.SaTempUtil;
import com.osfw.framework.constants.Constants;
import com.osfw.framework.constants.UserConstants;
import com.osfw.framework.enums.OnlineStatus;
import com.osfw.framework.enums.UserStatus;
import com.osfw.framework.exception.UserException;
import com.osfw.framework.manager.AsyncFactory;
import com.osfw.framework.manager.AsyncManager;
import com.osfw.framework.utils.*;
import com.osfw.system.entity.manage.SysRole;
import com.osfw.system.entity.manage.SysUser;
import com.osfw.system.service.config.ISysConfigService;
import com.osfw.system.service.manage.ISysMenuService;
import com.osfw.system.service.manage.ISysUserService;
import org.apache.shiro.session.Session;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.*;

/**
 * 登录校验方法
 */
@Component
public class LoginService {
    /**
     * 同步session到数据库的周期 单位为毫秒（默认1分钟）
     */
    @Value("${saToken.session.dbSyncPeriod}")
    private int dbSyncPeriod;

    /**
     * 上次同步数据库的时间戳
     */
    private static final String LAST_SYNC_DB_TIMESTAMP = "LAST_SYNC_DB_TIMESTAMP";

    private Set<String> sessions = new HashSet<>();

    @Autowired
    private PasswordService passwordService;

    @Autowired
    private ISysUserService userService;

    @Autowired
    private ISysMenuService menuService;

    @Autowired
    private ISysConfigService configService;


    /**
     * 登录
     */
    public SysUser login(String username, String password) {
        // 用户名或密码为空 错误
        if (StringUtils.isEmpty(username) || StringUtils.isEmpty(password)) {
            AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("not.null.valid")));
            UserException.throwUserNotExistsException();
        }
        // 密码如果不在指定范围内 错误
        if (password.length() < UserConstants.PASSWORD_MIN_LENGTH
                || password.length() > UserConstants.PASSWORD_MAX_LENGTH) {
            AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
            UserException.throwUserPasswordNotMatchException();
        }
        // 用户名不在指定范围内 错误
        if (username.length() < UserConstants.USERNAME_MIN_LENGTH
                || username.length() > UserConstants.USERNAME_MAX_LENGTH) {
            AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
            UserException.throwUserPasswordNotMatchException();
        }
        // IP黑名单校验
        String blackStr = configService.selectConfigByKey("sys.login.blackIPList");
        if (IpUtils.isMatchedIp(blackStr, IpUtils.getIpAddr(ServletUtils.getRequest()))) {
            AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.login.blocked")));
            UserException.throwBlackListException();
        }
        // 查询用户信息
        SysUser user = userService.selectUserByLoginName(username);
        if (user == null && maybeMobilePhoneNumber(username)) {
            user = userService.selectUserByPhoneNumber(username);
        }
        if (user == null && maybeEmail(username)) {
            user = userService.selectUserByEmail(username);
        }
        if (user == null) {
            AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.not.exists")));
            UserException.throwUserNotExistsException();
        }
        if (UserStatus.DELETED.getCode().equals(user.getDelFlag())) {
            AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.login.delete")));
            UserException.throwUserDeleteException();
        }
        if (UserStatus.DISABLE.getCode().equals(user.getStatus())) {
            AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.login.disbale")));
            StpUtil.disable(user.getUserId(), -1); // 永久封禁
            StpUtil.checkDisable(user.getUserId());
        }
        passwordService.validate(user, password);

        loginWithSaToken(user);

        AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success")));
        setRolePermission(user);
        recordLoginInfo(user.getUserId());
        return user;
    }

    private boolean maybeEmail(String username) {
        if (!username.matches(UserConstants.EMAIL_PATTERN)) {
            return false;
        }
        return true;
    }

    private boolean maybeMobilePhoneNumber(String username) {
        if (!username.matches(UserConstants.MOBILE_PHONE_NUMBER_PATTERN)) {
            return false;
        }
        return true;
    }

    /**
     * 设置角色权限
     *
     * @param user 用户信息
     */
    private void setRolePermission(SysUser user) {
        List<SysRole> roles = user.getRoles();
        if (!roles.isEmpty()) {
            // 设置permissions属性，以便数据权限匹配权限
            for (SysRole role : roles) {
                Set<String> rolePerms = menuService.selectPermsByRoleId(role.getRoleId());
                role.setPermissions(rolePerms);
            }
        }
    }

    private void loginWithSaToken(SysUser user) {
        StpUtil.login(user.getUserId(), getDevice());

        SaSession session = StpUtil.getSession();
        session.set("user", user);
        session.set("loginName", user.getLoginName());
        session.set("userName", user.getUserName());
        session.set("userAvatar", user.getAvatar() == null ? "" : "data:image/png;base64," + user.getAvatar());
        session.set("userPost", StringUtils.defaultIfEmpty(user.getUserPost(), ""));
        session.set("isAgreement", "0");
        CookieUtils.removeCookie(ServletUtils.getRequest(), ServletUtils.getResponse(), Constants.TEMP_TOKEN_NAME);

        sessions.add(session.getId());
    }

    public void logout(Object loginId) {
        // 记录登出日志
        String loginName = StpUtil.getSession().getString("loginName");
        AsyncManager.me().execute(AsyncFactory.recordLogininfor(loginName, Constants.LOGOUT, MessageUtils.message("user.logout.success")));

        logoutWithSaToken(loginId);

        CookieUtils.removeCookie(ServletUtils.getRequest(), ServletUtils.getResponse(), StpUtil.getTokenName());

        // 生成新的临时token的cookie
        String tempToken = SaTempUtil.createToken(Constants.TEMP_TOKEN_NAME, Constants.TEMP_TOKEN_TIMEOUT);
        CookieUtils.setCookie(ServletUtils.getResponse(), Constants.TEMP_TOKEN_NAME, tempToken, (60 - DateUtils.getMinute(new Date())) * 60);
    }

    private void logoutWithSaToken(Object loginId) {
        StpUtil.logout(loginId, getDevice());
    }

    // 获取客户端操作系统
    private String getDevice() {
        return String.join(" ", Constants.APPLICATION_NAME, UserAgentUtil.getOperatingSystem());
    }

    /**
     * 记录登录信息
     *
     * @param userId 用户ID
     */
    private void recordLoginInfo(Long userId) {
        SysUser user = new SysUser();
        user.setUserId(userId);
        user.setLoginIp(IpUtils.getIpAddr(ServletUtils.getRequest()));
        user.setLoginDate(new Date());
        userService.updateUserInfo(user);
    }

    /**
     * 更新会话；如更新会话最后访问时间/停止会话/设置超时时间/设置移除属性等会调用
     */
    public void syncSessionToDb(SaSession session) {
        Date lastSyncTimestamp = (Date) session.get(LAST_SYNC_DB_TIMESTAMP);
        if (lastSyncTimestamp != null) {
            boolean needSync = true;
            long deltaTime = DateUtils.getNowDate().getTime() - lastSyncTimestamp.getTime();
            if (deltaTime < dbSyncPeriod * 60 * 1000) {
                // 时间差不足 无需同步
                needSync = false;
            }
            // isGuest = true 访客
            boolean isGuest = session.getLoginId() == null || (Long) session.getLoginId() == 0L;

            // session 数据变更了 同步
            if (!isGuest) {
                needSync = true;
            }

            if (!needSync) {
                return;
            }
        }
        // 更新上次同步数据库时间
        session.set(LAST_SYNC_DB_TIMESTAMP, DateUtils.getNowDate().getTime());
        sessions.add(session.getId());
        AsyncManager.me().execute(AsyncFactory.syncSessionToDb(session));
    }

    /**
     * 当会话过期/停止（如用户退出时）属性等会调用
     */
    protected void doDeleteSession(SaSession session) {
        if (null == session) {
            return;
        }
        sessions.remove(session.getId());
    }

}
